﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Common;
using System.Configuration;
using System.Data;
using System.Configuration.Provider;
using System.Collections.Specialized;

namespace WorkNodeFramework
{
    public abstract class DataProvider:ProviderBase
    {
        List<ITable> cachedTables = new List<ITable>();
        public DataProvider() { }

        public DataProvider(string connectionString) {
            this.connectionString = connectionString;
        }

        string connectionString;
        public string DefaultConnectionString {
            get {
                if (connectionString == null) {
                    connectionString = ConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString;
                }
                return connectionString;
            }
        }

        public abstract DbParameter CreateParameter(string parameterName,object value);
        public abstract DbConnection CreateConnection();
        public abstract DbConnection CreateConnection(string connectionString);
        public abstract string GetParameterName(string baseName);
        public abstract DbCommand CreateCommand();
        public abstract DbDataAdapter CreateDataAdapter();
        public abstract string QueryExpressioṇ̣̣̣̣́(IQueryExpression queryExpression);
        public abstract string QualifyTableName(ITable table);
        public abstract string QualifyColumnName(Column column);
        internal abstract string GenerateJoinClause(IJoinClause join);
        internal abstract string GenerateWhereCondition(WhereCondition condition, ParameterIndexCollection parameters);
        internal abstract string GenerateWhereComplexCondition(IWhereCondition condition, ParameterIndexCollection parameters);
        internal abstract string BuildQueryStatement(Query query, ParameterIndexCollection parameters);
        internal abstract string BuildSelectQueryStatement<T>(SelectQuery<T> select, ParameterIndexCollection parameters)
            where T : SelectQuery<T>;
        internal abstract string BuildDeleteQueryStatement(Delete delete, ParameterIndexCollection parameters);
        internal abstract string BuildUpdateQueryStatement(Update update, ParameterIndexCollection parameters);
        internal abstract string BuildInsertQueryStatement(Insert insert, ParameterIndexCollection parameters);
        internal abstract string BuildInsertIntoQueryStatement(InsertInto insert, ParameterIndexCollection parameters);
        internal abstract ITable LoadTable(string name);
        internal abstract T LoadTable<T>(string name)
            where T:ITable;
        public abstract TableProvider TableProvider{get;}

        [ThreadStatic]
        private static DbConnection __sharedConnection;

        public DbConnection CurrentSharedConnection
        {
            get { return __sharedConnection; }

            protected set
            {
                if (value == null)
                {
                    __sharedConnection.Dispose();
                    __sharedConnection = null;
                }
                else
                {
                    __sharedConnection = value;
                    __sharedConnection.Disposed += __sharedConnection_Disposed;
                }
            }
        }

        private static void __sharedConnection_Disposed(object sender, EventArgs e)
        {
            __sharedConnection = null;
        }

        public ITable GetTable(string name)
        {
            foreach (ITable table in cachedTables)
                if (string.Compare(table.Name, name, true) == 0)
                    return table;
            Table newTable = LoadTable<Table>(name);
            cachedTables.Add(newTable);
            return newTable;
        }

        public T GetTable<T>(string name) 
            where T:ITable
        {
            ITable table = GetTable(name);
            ITable result = table;
            if (!(table is T)) {
                result = (T)Activator.CreateInstance(typeof(T), this, name);
                result.Columns.Clear();
                foreach (Column col in table.Columns)
                    result.Columns.Add(col);
                cachedTables[cachedTables.IndexOf(table)] = result;
            }
            if (result == null)
            {
                result = LoadTable<T>(name);
                cachedTables.Add(result);
            }
            return (T)result;
        }

        public int ExecuteNonQuery(string cmdText, params DbParameter[] parameters)
        {
            int effect = -1;
            AutomaticConnectionScope cs = new AutomaticConnectionScope(this);
            DbCommand cmd = CreateCommand();
            cmd.CommandText = cmdText;
            foreach (DbParameter par in parameters)
                cmd.Parameters.Add(par);
            cmd.Connection = cs.Connection;
            try
            {
                effect = cmd.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                cs.Dispose();
                throw ex;
            }

            return effect;
        }

        public void ExecuteTransaction(params ExecuteCommandManager[] commands)
        {
            if (commands.Length == 0)
                return;
            List<ExecuteCommand> cmdList = new List<ExecuteCommand>();
            foreach (ExecuteCommandManager cm in commands) {
                foreach (ExecuteCommand e in cm.ExecuteCommands)
                    cmdList.Add(e);
            }
            
            ExecuteTransaction(cmdList.ToArray());

            foreach (ExecuteCommandManager cm in commands)
                cm.OnExecuteCommandsCompleted(EventArgs.Empty);
        }

        public void ExecuteTransaction(params IExecuteCommand[] commands)
        {
            if (commands.Length == 0)
                return;
            ExecuteCommand[] ecmds = new ExecuteCommand[commands.Length];
            for (int i = 0; i < commands.Length; i++)
                ecmds[i] = commands[i].BuildExecuteCommand();
            ExecuteTransaction(ecmds);
        }

        public void ExecuteTransaction(params ExecuteCommand[] commands)
        {
            if (commands.Length == 0)
                return;
            using (AutomaticConnectionScope cs = new AutomaticConnectionScope(this))
            {
                if (cs.Connection.State == ConnectionState.Closed)
                    cs.Connection.Open();
                DbTransaction trans = cs.Connection.BeginTransaction();
                foreach (ExecuteCommand ecomd in commands)
                {
                    DbCommand cmd = CreateCommand();
                    cmd.Connection = cs.Connection;
                    cmd.Transaction = trans;
                    cmd.CommandText = ecomd.CmdText;
                    if(ecomd.Parameters!=null && ecomd.Parameters.Length>0)
                        cmd.Parameters.AddRange(ecomd.Parameters);
                    try
                    {
                        cmd.ExecuteNonQuery();
                    }
                    catch (DbException ex)
                    {
                        trans.Rollback();
                        throw ex;
                    }
                }
                trans.Commit();
            }
        }

        internal void ExecuteTransaction(DbConnection con, DbTransaction trans, params IExecuteCommand[] commands)
        {
            if (commands.Length == 0)
                return;
            ExecuteCommand[] ecmds = new ExecuteCommand[commands.Length];
            for (int i = 0; i < commands.Length; i++)
                ecmds[i] = commands[i].BuildExecuteCommand();
            ExecuteTransaction(con,trans, ecmds);
        }

        internal void ExecuteTransaction(DbConnection con, DbTransaction trans,params ExecuteCommand[] commands)
        {
            if (commands.Length == 0)
                return;
            foreach (ExecuteCommand ecomd in commands)
            {
                DbCommand cmd = con.CreateCommand();
                cmd.CommandText = ecomd.CmdText;
                cmd.Transaction = trans;
                if (ecomd.Parameters != null && ecomd.Parameters.Length > 0)
                    cmd.Parameters.AddRange(ecomd.Parameters);
                cmd.ExecuteNonQuery();
            }
        }

        public DataTable GetSchema(string cmdText) {
            DataTable data = null;
            IDataReader reader = ExecuteReader(cmdText);
            data = reader.GetSchemaTable();
            reader.Close();
            return data;
        }

        public IDataReader ExecuteReader(string cmdText,params DbParameter[] parameters)
        {
            IDataReader reader = null;
            AutomaticConnectionScope cs = new AutomaticConnectionScope(this);
            DbCommand cmd = CreateCommand();
            cmd.CommandText = cmdText;
            foreach (DbParameter par in parameters)
                cmd.Parameters.Add(par);
            cmd.Connection = cs.Connection;
            try
            {
                reader = cs.IsUsingSharedConnection ? cmd.ExecuteReader() : cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
            catch(Exception ex) 
            {
                cs.Dispose();
                throw ex;
            }

            return reader;
        }

        public object ExecuteScalar(string cmdText, params DbParameter[] parameters)
        {
            object result = null;
            AutomaticConnectionScope cs = new AutomaticConnectionScope(this);
            DbCommand cmd = CreateCommand();
            cmd.CommandText = cmdText;
            foreach (DbParameter par in parameters)
                cmd.Parameters.Add(par);
            cmd.Connection = cs.Connection;
            try
            {
                result = cmd.ExecuteScalar();
            }
            catch (Exception ex)
            {
                cs.Dispose();
                throw ex;
            }

            return result;
        }

        DbDataAdapter CreateDataAdapter(DbConnection con,string cmdText, params DbParameter[] parameters)
        {
            DbCommand cmd = CreateCommand();
            cmd.CommandText = cmdText;
            foreach (DbParameter par in parameters)
                cmd.Parameters.Add(par);
            cmd.Connection = con;
            DbDataAdapter da = CreateDataAdapter();
            da.SelectCommand = cmd;
            return da;
        }

        public DataSet ExecuteDataSet(string cmdText, params DbParameter[] parameters)
        {
            DataSet ds = new DataSet();
            AutomaticConnectionScope cs = new AutomaticConnectionScope(this);
            DbDataAdapter da = CreateDataAdapter(cs.Connection, cmdText, parameters);
            try
            {
                da.Fill(ds);
            }
            catch (Exception ex)
            {
                cs.Dispose();
                throw ex;
            }

            return ds;
        }

        public DataTable ExecuteDataTable(string cmdText, params DbParameter[] parameters)
        {
            DataTable table = new DataTable();
            AutomaticConnectionScope cs = new AutomaticConnectionScope(this);
            DbDataAdapter da = CreateDataAdapter(cs.Connection, cmdText, parameters);
            try
            {
                da.Fill(table);
            }
            catch (Exception ex)
            {
                cs.Dispose();
                throw ex;
            }

            return table;
        }

        string connectionStringName;
        public string ConnectionStringName {
            get {
                return connectionStringName;
            }
        }

        public override void Initialize(string name, NameValueCollection config)
        {
            base.Initialize(name, config);
            connectionStringName = config[ConfigurationPropertyName.CONNECTION_STRING_NAME];
        }

        internal void ResetSharedConnection()
        {
            __sharedConnection = null;
        }

        internal DbConnection InitializeSharedConnection(string sharedConnectionString)
        {
            if (CurrentSharedConnection == null)
                CurrentSharedConnection = CreateConnection(sharedConnectionString);

            return CurrentSharedConnection;
        }

        internal DbConnection InitializeSharedConnection()
        {
            if (CurrentSharedConnection == null)
                CurrentSharedConnection = CreateConnection();

            return CurrentSharedConnection;
        }
    }

    /// <summary>
    /// Summary for the DataProviderCollection class
    /// </summary>
    public class DataProviderCollection : ProviderCollection
    {
        private static readonly object _lockProvider = new object();

        /// <summary>
        /// Gets the <see cref="SubSonic.DataProvider"/> with the specified name.
        /// </summary>
        /// <value></value>
        public new DataProvider this[string name]
        {
            get { return (DataProvider)base[name]; }
        }

        /// <summary>
        /// Adds a provider to the collection.
        /// </summary>
        /// <param name="provider">The provider to be added.</param>
        /// <exception cref="T:System.NotSupportedException">The collection is read-only.</exception>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="provider"/> is null.</exception>
        /// <exception cref="T:System.ArgumentException">The <see cref="P:System.Configuration.Provider.ProviderBase.Name"/> of <paramref name="provider"/> is null.- or -The length of the <see cref="P:System.Configuration.Provider.ProviderBase.Name"/> of <paramref name="provider"/> is less than 1.</exception>
        /// <PermissionSet>
        /// 	<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        public override void Add(ProviderBase provider)
        {
            if (provider == null)
                throw new ArgumentNullException("provider");

            if (!(provider is DataProvider))
                throw new ArgumentException("Invalid provider type", "provider");

            if (base[provider.Name] == null)
            {
                lock (_lockProvider)
                {
                    if (base[provider.Name] == null)
                        base.Add(provider);
                }
            }
        }
    }
}
